home *** CD-ROM | disk | FTP | other *** search
/ Libris Britannia 4 / science library(b).zip / science library(b) / CUGUK / COMMS / C022.ZIP / MORE.C < prev    next >
Text File  |  1990-01-31  |  31KB  |  1,276 lines

  1. /************************************************************************
  2.  * C Users Group (U.K) C Source Code Library File    00000000    *
  3.  * Inquiries to: M. Houston, 36 Whetstone Clo. Farquhar Rd.        *
  4.  * Edgbaston, Birmingham B15 2QN ENGLAND                *
  5.  ************************************************************************
  6.  * File name:    more.c                            *
  7.  * Program  :    more                            *
  8.  *                                     *
  9.  * Source   :    Adrian Godwin, with some code from Minix's editor    *
  10.  * Purpose  :    file or pipe paginator                    *
  11.  * Changes  :                                *
  12.  *                                    *
  13.  * 12.06.88    A Godwin - release to CUG                *
  14.  ************************************************************************/
  15.  
  16. /* An implementation of the Berkeley 'more' utility for MINIX. 
  17.  * 
  18.  * Most of the paging commands are supported ('v' gets mined, not vi, and
  19.  * doesn't go to the current line number), but only window size, line number
  20.  * search and help options are supported on the command line in this initial
  21.  * version.
  22.  * MINIX terminal i/o escape codes are built in, rather than using TERMCAP.
  23.  * Two additional paging commands, 'e' and 'w', perform screen repaint and
  24.  * current screen write-out to a file, respectively.
  25.  * 
  26.  * Search routines are those by Michiel Huisjes, as used in mined.
  27.  *    - these are much smaller than those in the regex library routiines,
  28.  *    and have all the functionality required here.
  29.  *
  30.  * The MINIX library showed some bugs, which need fixing before this will
  31.  * work properly:
  32.  * 
  33.  *    fseek() & getc() handle the buffer count wrongly : this causes
  34.  *    an incorrect value to be returned by ftell(). The effect is to
  35.  *     generate a blank line if an attempt is made to return to the 
  36.  *    starting point of a search (also when a search fails).
  37.  *    system() does not exist.
  38.  *    puts() is defined in stdio.h as fputs(s,stdout). It should be a 
  39.  *    separate function, appending a newline character. To avoid problems
  40.  *    where this is not fixed, fputs() and putchar('\n') are used.
  41.  *
  42.  * The assembler asld needs a largish (78K) temporary file to compile this:
  43.  * use the -T. flag to avoid using /tmp.
  44.  * Reduce the program's stack size to about 10000 with chmem.
  45.  * The help screen displayed when the interactive command 'h' is used is
  46.  * actually obtained from a help file, rather than built into the program.
  47.  * This file is normally /usr/lib/more.hlp - defined by the global string
  48.  * 'helpfile'.
  49.  *
  50.  */
  51.  
  52. #define VERSION        "1.1  (12.06.88)"
  53.  
  54.  
  55. #include <stdio.h>
  56. #include <sgtty.h>
  57. #include <signal.h>
  58. #include <stat.h>
  59.  
  60. #define TRUE    1
  61. #define FALSE    0
  62.  
  63. #define LONGSCRN 25    /* longest expected screen length */
  64. #define LINELEN  80    /* longest expected line length */
  65.  
  66. #define AGAIN  -32000    /* display the same file again */
  67. #define COLON    0x8000    /* command letter modified by ':' */
  68.  
  69. #define BLOCK_SIZE    1024    /* used to define size of compiled REGEX */
  70.  
  71. #define BACK    ('\\')    /* special character escape */
  72. #define KILL    ('@')    /* should come from TIOCGETC */
  73. #define ERASE    ('\b')
  74.  
  75. /* default options (may get changed by command line or commands */
  76.  
  77. char *spattern    = "";        /* search pattern             */
  78. int  firstline    = 0;        /* first line to read in file         */
  79. int  window    = LONGSCRN-2;    /* new lines to scroll per screen     */
  80. int  scroll    = 11;        /* lines in a scroll             */
  81. int  disp_cons  = TRUE;        /* display control chars as ^ch        */
  82.  
  83. /* globals */
  84.  
  85. /* This string defines where the help file (displayed when h is given
  86.  * at the interactive prompt) is found.
  87.  */
  88.  
  89. char *helpfile  = "/usr/lib/more.hlp";
  90.  
  91.  
  92. /* This string defines the text editor invoked when v is given at the
  93.  * interactive prompt. Mined cannot be informed which line to open
  94.  * the edit at.
  95.  */
  96.  
  97. char *editor    = "mined";
  98.  
  99. int  input_fd    = 0;    /* more's control stream */
  100. int  quit    = FALSE;/* true if SIGQUIT received */
  101. int  repeat    = 1;    /* repeat count for a few commands */
  102. char *filename  = NULL; /* name of current file */
  103. FILE *filep    = NULL;    /* current file pointer */
  104. int  lineno    = 0;    /* current line number */
  105. int  eoflag    = FALSE;/* read end-of-file */
  106. long filelength    = -1L;    /* length of current file */
  107. long searchfrom = 0L;    /* fseek() where last search started */ 
  108. int  searchline = 0;    /* lineno where last search started    */
  109. char inline[LINELEN];    /* character input line buffer */
  110. char *sline[LONGSCRN];  /* pointers to a screenfull of line buffers */
  111. int  linesused  = 0;    /* number of linebuffers with any contents */
  112.  
  113. extern FILE *fopen();
  114. extern long ftell();
  115. extern char *fgets();
  116.  
  117. /* screen control settings - could be read from termcap, if there was one */
  118.  
  119.  
  120. char *pclear     = "\r\033~0";    /* clear current line (and all after it) */
  121. char *invon    = "\033zp";    /* inverse video */
  122. char *invoff    = "\033z\07";    /* normal video  */
  123.  
  124. int scrlen     = LONGSCRN;    /* lines in a screenfull (window size) */
  125. int scrwid    = LINELEN;    /* columns in a screenwidth */
  126.  
  127.  
  128.  
  129. panic(s,t)
  130. char *s,*t;
  131. {
  132.   fprintf(stderr,"more failed : %s%s\n", s, t);
  133.   cleanup();
  134. }
  135.  
  136. usage(s,t)
  137. char *s,*t;
  138. {
  139.   fprintf(stderr,"more   : %s %s\n", s, t);
  140.   fprintf(stderr,"usage  : more [-n] [+linenumber] [+/pattern] [file ...]\n");
  141.   cleanup();
  142. }
  143.  
  144.  
  145.  
  146. main(argc,argv)
  147. int argc;
  148. char *argv[];
  149. {
  150.   int cmd = 0;            /* first operation on file */
  151.   char *errbuf, *scrbuf, *malloc();
  152.   char **first, **last;        /* first and last file args in cmd line */
  153.   int step,i;
  154.  
  155.   /* setup the io */
  156.  
  157.   if ((errbuf = malloc(BUFSIZ)) == NULL)
  158.     panic("no memory for buffers","");
  159.  
  160.   setbuf(stderr,errbuf);
  161.   perprintf(stdout);
  162.   perprintf(stderr);
  163.  
  164.   if (!isatty(0))     /* standard input not a tty - open /dev/tty */
  165.     open_device();
  166.   raw_mode(TRUE);
  167.  
  168.   /* allocate buffers for screenfull of text */
  169.  
  170.   if ((scrbuf = malloc((scrwid+1) * LONGSCRN)) == NULL)
  171.     panic("no memory for line buffers","");
  172.  
  173.   for (i = 0; i < LONGSCRN; i++) {
  174.     sline[i] = scrbuf;
  175.     scrbuf += scrwid+1;
  176.   }
  177.  
  178.   /* unbundle the options */
  179.  
  180.   while( argv++, --argc ) {
  181.  
  182.     if (**argv == '-') {
  183.  
  184.         /* help argument */
  185.         if ((*argv)[1] == 'h')
  186.             usage("version ",VERSION);
  187.  
  188.  
  189.         /* stdin argument */
  190.         else if ((*argv)[1] == '\0')
  191.             break;
  192.  
  193.         /* lines-per-page argument */
  194.         else 
  195.             window = narg((*argv)+1);
  196.  
  197.     }
  198.     else if (**argv == '+') {
  199.  
  200.         /* starting pattern argument */
  201.         if ((*argv)[1] == '/') {
  202.             spattern = (*argv)+2;
  203.             cmd = 'n';
  204.         }
  205.  
  206.         /* starting line argument */
  207.         else {
  208.             repeat = narg((*argv)+1);
  209.             cmd = 's';
  210.         }
  211.     }
  212.     else
  213.         break;    /* no more flags - do the files */
  214.   }
  215.  
  216.   first = argv;
  217.   last  = argv + argc - 1;
  218.   while (*argv != NULL) {
  219.  
  220.     step = more(*argv,cmd);    
  221.  
  222.     /* step returned as :
  223.         +ve   : skip forwards
  224.         -ve   : skip backwards
  225.         AGAIN : skip to start of this file
  226.     */
  227.  
  228.     if (step == AGAIN)
  229.         step = 0;
  230.  
  231.     if (argv == last && step == 1)
  232.         break;        /* off end of file list */
  233.  
  234.     if (!eoflag)
  235.         printf("\nskipping ...\n");
  236.  
  237.     argv += step;
  238.     if (argv > last) {    /* clip to limits of list */
  239.         argv = last;
  240.         printf("skipping back to file %s ..\n\n",*argv);
  241.     }
  242.     else if (argv < first) {
  243.         argv = first;
  244.         printf("skipping to file %s ..\n\n",*argv);
  245.     }
  246.     else
  247.         printf("starting file %s\n\n",*argv);
  248.  
  249.     printf("%s%s next file : %s %s",pclear,invon,*argv,invoff);
  250.     cmd = getcom();
  251.   }
  252.  
  253.   if (argc == 0) { /* no files specified - do standard input */
  254.  
  255.     if (isatty(0))
  256.         usage("no input stream","");
  257.     else
  258.         more("-",cmd);
  259.   }
  260.  
  261.   cleanup();
  262. }
  263.  
  264.  
  265. /* narg() - extract a decimal argument from the string or fatal error. */
  266.  
  267. narg(s)
  268. char *s;
  269. {
  270.   int res;
  271.  
  272.   if (s == NULL || sscanf(s, "%d", &res) != 1) 
  273.     usage("bad numeric argument ",s == NULL ? "" : s);
  274.  
  275.   return res;
  276. }
  277.  
  278. /* Routine to open terminal when more is used in a pipeline. */
  279. open_device()
  280. {
  281.   if ((input_fd = open("/dev/tty", 0)) < 0)
  282.     panic("Cannot open /dev/tty for read","");
  283. }
  284.  
  285. getcc()
  286. {
  287.   char c = 0;
  288.  
  289.   while (read(input_fd, &c, 1) != 1 && quit == FALSE)
  290.     ;/* wait for an input char, not a SIG. */
  291.  
  292.   return c & 0377;
  293. }
  294.  
  295. getcom()
  296. {
  297.   int c = '0';
  298.   int arg = FALSE;
  299.   static int lastrepeat = 1;
  300.  
  301.   lastrepeat = repeat;
  302.   repeat = 0;
  303.  
  304.   while ( (c=getcc()) >= '0' && c <= '9') {
  305.     repeat = (repeat * 10) + (c - '0');
  306.     arg = TRUE;
  307.   }
  308.  
  309.   if (arg == FALSE) repeat = 1;    /* default repeat count */
  310.  
  311.   if (c == ':')            /* command prefix */
  312.     c = getcc() | COLON;
  313.  
  314.   if (c == '.')
  315.     repeat = lastrepeat;
  316.  
  317.   return c;
  318. }
  319.  
  320. char *getstring(prompt,buf)
  321. char *prompt, *buf;
  322. {
  323.   int incount = 0;
  324.   int c;
  325.   char erase = ERASE;
  326.   char kill  = KILL;
  327.   struct sgttyb sbuf;
  328.  
  329.   printf("%s%s",pclear,prompt);
  330.  
  331.   if (ioctl(input_fd,TIOCGETP,&sbuf) == 0) {
  332.     erase = sbuf.sg_erase;
  333.     kill  = sbuf.sg_kill;
  334.   }
  335.  
  336.   while (quit == FALSE && (c = getcc()) != '\n') {
  337.  
  338.     if (c == erase) {
  339.         if (incount--)
  340.             printf("\b \b");
  341.         else {
  342.             printf("%s%s",invoff,pclear);
  343.             buf[0] = '\0';
  344.             return NULL;
  345.         }
  346.     }
  347.  
  348.     else if (c == kill) {
  349.         printf("%c\n%s",kill,prompt);
  350.         incount = 0;
  351.     }
  352.  
  353.     else {
  354.         if (c == BACK) {
  355.             putchar(c);
  356.             fflush(stdout);
  357.             c = getcc();
  358.         }
  359.  
  360.         if (incount >= LINELEN)
  361.             putchar('\07');
  362.         else {
  363.             putchar(c);
  364.             buf[incount++] = c;
  365.         }
  366.     }
  367.     fflush(stdout);
  368.   }
  369.   printf("%s",invoff);
  370.   buf[incount] = '\0';
  371.   return (quit ? NULL : buf);
  372. }
  373.  
  374.  
  375. int catch()
  376. {
  377.   signal(SIGQUIT, catch);
  378.   quit = TRUE;
  379. }
  380.  
  381. int icatch()
  382. {
  383.   signal(SIGINT, icatch);
  384.   howmuch();
  385. }
  386.  
  387.  
  388. int cleanup()
  389. {
  390.   printf("%s\n",pclear);
  391.   raw_mode(FALSE);
  392.   _cleanup();
  393.   exit(0);
  394. }
  395.  
  396. howmuch()
  397. {
  398.   int percent = -1;
  399.  
  400.   if (filelength > 0L) 
  401.     percent = ((ftell(filep) * 100L)/filelength);
  402.  
  403.   if (percent < 0 && !eoflag)
  404.     printf("%s%s --More-- %s",pclear,invon,invoff);
  405.   else if (!eoflag)
  406.     printf("%s%s --More-- (%d\%)%s",pclear, invon,percent, invoff);
  407.  
  408.   fflush(stdout);
  409. }
  410.  
  411. /*
  412.  * Set and reset tty into CBREAK or old mode 
  413.  * according to argument `state'. 
  414.  */
  415. int raw_mode(state)
  416. int state;
  417. {
  418.   static int saved = FALSE;
  419.   static struct sgttyb old_tty;
  420.   static struct sgttyb new_tty;
  421.  
  422.   if (state == FALSE) {
  423.     if (saved == FALSE)    /* ever set rawmode on ? */
  424.         return;    
  425.  
  426.     saved = FALSE;        /* force next set_raw to get settings */
  427.       ioctl(input_fd, TIOCSETP, &old_tty);
  428.       return;
  429.   }
  430.  
  431.   saved = TRUE;
  432.  
  433. /* Save old tty settings */
  434.   ioctl(input_fd, TIOCGETP, &old_tty);
  435.  
  436. /* Set tty to CBREAK mode */
  437.   ioctl(input_fd, TIOCGETP, &new_tty);
  438.   new_tty.sg_flags |= CBREAK;
  439.   new_tty.sg_flags &= ~ECHO;
  440.   ioctl(input_fd, TIOCSETP, &new_tty);
  441.  
  442. /* Catch quit signal (sets quit flag) and interrupt signal */
  443.   signal(SIGQUIT, catch);
  444.   signal(SIGINT, icatch);
  445. }
  446.  
  447. more(name,cmd)
  448. char *name;
  449. int  cmd;
  450. {
  451.   int res;
  452.   struct stat sbuf;
  453.  
  454.   eoflag = TRUE;
  455.   filelength = -1;
  456.  
  457.   if (*name == '-' && *(name+1) == '\0') {
  458.  
  459.     if (isatty(0)) {
  460.         printf("%sCannot read both input and commands from stdin\n",
  461.             pclear);
  462.         return 1;
  463.       }
  464.  
  465.     filep = stdin;
  466.     filename = "standard input";
  467.   }
  468.   else {
  469.     if ((filep = fopen(name,"r")) == NULL) {
  470.         printf("%scannot open file %s\n",pclear,name);
  471.         return 1;
  472.     }
  473.     filename = name;
  474.     if (fstat(filep->_fd, &sbuf) == 0)
  475.         filelength = sbuf.st_size;
  476.     if (filelength < 1L)
  477.         filelength = 1L;
  478.   }
  479.  
  480.   lineno = searchline = linesused = 0;
  481.   eoflag = FALSE;
  482.   searchfrom = 0L;
  483.  
  484.   /* do first & subsequent operations, until file exhausted */
  485.   while ((res = do_command(cmd)) == 0)
  486.     cmd = getcom();
  487.  
  488.   if (filep != stdin)
  489.     fclose(filep);
  490.  
  491.   return res;    /* main() uses this for stepping thro' command line */
  492. }
  493.  
  494. do_command(cmd)
  495. int cmd;
  496. {
  497.   static int lastcmd = 0;
  498.   char ibuf[LINELEN];
  499.   char pbuf[LINELEN];
  500.   FILE *fp;
  501.   int c,count;
  502.  
  503.   fputs(pclear,stdout);
  504.  
  505.   if (quit) {        /* command quitted before it's executed */
  506.     quit = FALSE;
  507.     howmuch();
  508.     return 0;
  509.   }
  510.  
  511.   if (cmd == '.') {
  512.     if (lastcmd == '/')    /* repeat search command = 'n' */
  513.         cmd = 'n';
  514.     else cmd = lastcmd;    /* repeat any other command */
  515.   }
  516.  
  517.   quit = FALSE;
  518.   lastcmd = cmd;
  519.  
  520.   switch (cmd) {
  521.  
  522.   case '\n':
  523.   case '\r':
  524.     return lines(1);
  525.  
  526.   case ' ':
  527.   case  0:
  528.     return lines(repeat > 1 ? repeat : window);
  529.  
  530.   case '=':
  531.     printf("%s%sline %d %s",pclear,invon,lineno,invoff);
  532.     return 0;
  533.  
  534.   case '/':
  535.     if ( (spattern = getstring("/",ibuf)) == NULL) {
  536.         howmuch();
  537.         return 0;
  538.     }
  539.     return search(spattern);
  540.  
  541.   case '\'':
  542.     if (filep != stdin) {
  543.         fseek(filep,searchfrom,0);
  544.         lineno = searchline;
  545.         return lines(window);
  546.     }
  547.     return 0;
  548.  
  549.   case '!':
  550.     if (getstring("!",ibuf) == NULL) {
  551.         howmuch();
  552.         return 0;
  553.     }
  554.     go_exec(ibuf);
  555.     return 0;
  556.  
  557.   case 'v':    /* should be vi, but mined will have to do */
  558.     sprintf(ibuf,"%s %s",editor,filename);
  559.     go_exec(ibuf);
  560.     return 0;
  561.  
  562.   case 'd':
  563.   case  4:
  564.     if (repeat > 1)
  565.         scroll = repeat;
  566.     return lines(scroll);
  567.  
  568.   case 'e':
  569.   case  5:
  570.     repaint();
  571.     return 0;
  572.  
  573.   case 'f':
  574.     skip(repeat * window);
  575.     return lines(window-3);
  576.  
  577.   case 'f'|COLON:
  578.     printf("%s%sfile '%s', line %d %s",
  579.         pclear,invon,filename,lineno,invoff);
  580.     return 0;
  581.  
  582.   case 'h':
  583.     if ((fp = fopen(helpfile,"r")) == NULL) {
  584.         printf("%s%sno help available - missing %s%s\n",
  585.             pclear,invon,helpfile,invoff);
  586.         return 0;
  587.     }
  588.  
  589.     /* copy out the help file */
  590.     putchar('\n');
  591.     while ((c = getc(fp)) != EOF)
  592.         putchar(c);
  593.     fclose(fp);
  594.     fflush(stdout);
  595.     getcc();
  596.     repaint();
  597.     return 0;
  598.  
  599.   case 'n':
  600.     return search(spattern);    /* search again */
  601.  
  602.   case 'n'|COLON:
  603.     return (repeat);
  604.  
  605.   case 'p'|COLON:
  606.     if (filep == stdin) {
  607.         printf("\07");
  608.         return 0;
  609.     }
  610.     if (repeat == 1 && lineno > window)
  611.         return AGAIN;        /* start of file */
  612.     else
  613.         return -repeat;        /* skip back */
  614.  
  615.   case 'q':
  616.   case 'Q':
  617.   case 'q'|COLON:
  618.   case 'Q'|COLON:
  619.     cleanup();
  620.     exit(0);
  621.  
  622.   case 's':
  623.     skip(repeat);
  624.     return lines(window-3);
  625.  
  626.   case 'w':
  627.     count = (repeat > 1 ? repeat : window);
  628.     sprintf(pbuf,"write %d lines to file: ",count);
  629.     if (getstring(pbuf,ibuf) == NULL) {
  630.         howmuch();
  631.         return 0;
  632.     }
  633.     return wrt_lines(ibuf,count);
  634.  
  635.   case 'z':
  636.     if (repeat > 1)
  637.         window = repeat;
  638.     return lines(window);
  639.  
  640.   }
  641.   printf("\\%o not implemented",cmd);
  642.   return 0;
  643. }
  644.  
  645. /* repaint all the lines back on the screen */
  646.  
  647. repaint()
  648. {
  649.   int i;
  650.  
  651.   putchar('\n');
  652.   for (i = linesused; i > 0; i--) {
  653.     fputs(sline[i-1],stdout);
  654.     putchar('\n');
  655.   }
  656.   fflush(stdout);
  657. }
  658.  
  659. /* get the next line (or partial line) from the input file */
  660.  
  661. char *getline()
  662. {
  663.   char *p;
  664.   int i, ch;
  665.  
  666.   /* rotate buffer pointers so latest lines are at the top */
  667.   p = sline[LONGSCRN-1];
  668.   for (i = LONGSCRN-1; i > 0; i--)
  669.     sline[i] = sline[i-1];
  670.   sline[0] = p;
  671.  
  672.   /* indicate how many line buffers are available */
  673.   if (linesused < LONGSCRN)
  674.     linesused++;
  675.   else
  676.     linesused = LONGSCRN;
  677.  
  678.  
  679.   /* read a line, or a screen width, into the current line buffer */
  680.  
  681.   if (feof(filep) || quit == TRUE)
  682.     return NULL;
  683.  
  684.   for (p = *sline, i = 0; i < scrwid; i++) {
  685.  
  686.     switch(ch = getc(filep)) {
  687.  
  688.     case '\n':
  689.     case EOF:
  690.         ch = '\0';
  691.         lineno++;        /* real line end - terminate line */
  692.         i = scrwid;        /* force end of read */
  693.         break;
  694.  
  695.     case '\t':
  696.         i += 7 - (i % 8);    /* count tabs to stop line ...     */
  697.         if (i >= scrwid)    /* .. overflowing uncounted.    */
  698.             ch = ' ';
  699.         break;
  700.  
  701.     default:            /* display control chars as ^C     */
  702.         if (ch < ' ') {
  703.             if (disp_cons) {
  704.                 if (++i < scrwid) {
  705.                     *p++ = '^';
  706.                     ch += '@';
  707.                 }
  708.                 else
  709.                     ch = '^';
  710.             }
  711.         }
  712.     }
  713.     *p++ = ch;
  714.   }
  715.   *p++ = '\0';        /* terminate counted-out line */
  716.  
  717.   return *sline;
  718. }
  719.  
  720.  
  721. skip(n)
  722. int n;
  723. {
  724.   if (n < 1)
  725.     return;
  726.  
  727.   printf("\nskipping ... \n\n");
  728.   while (n-- > 0 && getline() != NULL)
  729.     ;
  730. }
  731.  
  732. lines(n)
  733. int n;
  734. {
  735.   char *lread;
  736.  
  737.   if (eoflag)        /* don't read past end of file */
  738.     return 1;
  739.  
  740.   fputs(pclear,stdout);
  741.   while (n-- > 0 && (lread = getline()) != NULL) {
  742.     fputs(lread,stdout);
  743.     putchar('\n');
  744.   }
  745.   if (lread == NULL && quit == FALSE) eoflag = TRUE;
  746.  
  747.   howmuch();    /* display     --More-- (%nn) prompt */
  748.  
  749.   return (eoflag ? 1 : 0);
  750. }
  751.  
  752.  
  753. /* call system(), tidying up the screen around it. */
  754.  
  755. go_exec(cmd)
  756. char *cmd;
  757. {
  758.     int res;
  759.  
  760.     printf("\n");
  761.     raw_mode(FALSE);
  762.     res = system(cmd);
  763.     raw_mode(TRUE);
  764.     printf("\n\n");
  765.     howmuch();
  766.  
  767.     return res;
  768. }
  769.  
  770. /* write part of the file out to another - 'count' lines, starting 
  771.  * with those in the window.
  772.  */
  773.  
  774. wrt_lines(name,count)
  775. char *name;
  776. int count;
  777. {
  778.   int bcount,ret = 0;
  779.   FILE *fp;
  780.   char *lread = "";    /* not NULL */
  781.  
  782.   if ((fp = fopen(name, "w")) == NULL) {
  783.     printf("%s%scannot create file %s%s",pclear,invon,name,invoff);
  784.     return 0;
  785.   }
  786.  
  787.   fputs(pclear,stdout);
  788.   bcount = (linesused < window ? linesused : window);
  789.   bcount = (bcount < count ? bcount : count);
  790.   count -= bcount;
  791.  
  792.   /* write out as much as possible from the line buffers */
  793.   while (bcount-- > 0)
  794.     fprintf(fp, "%s\n", sline[bcount]);
  795.  
  796.   /* write out the remainder by reading (and displaying) the file */
  797.   while (count-- > 0 && !eoflag && (lread = getline()) != NULL) {
  798.  
  799.     fputs(lread,stdout);    /* write the line to the screen */
  800.     putchar('\n');
  801.  
  802.     fputs(lread,fp);    /* write the line to the file */
  803.     putc('\n',fp);
  804.  
  805.   }
  806.   if (lread == NULL && quit == FALSE) eoflag = TRUE;
  807.  
  808.   fclose(fp);
  809.   howmuch();
  810.     
  811.   /* if we hit the end of the file, wait unless it's stdin. */
  812.   if (filep == stdin && eoflag)
  813.     return 1;
  814.   else
  815.     return 0;
  816. }
  817.  
  818.  
  819. /*  ================================================================  *
  820.  *            Search Routines                      *    
  821.  *  ================================================================  *
  822.  *
  823.  *  These routines are poached directly from MINED source with only minor
  824.  *  modifications (simplifications for MORE). All credits to the original 
  825.  *  writer, Michiel Huisjes.
  826.  *  The library regex routines were not used, because they are much more 
  827.  *  general than these, and are bigger than the whole of this utility!
  828.  *
  829.  * A regular expression consists of a sequence of:
  830.  *     1. A normal character matching that character.
  831.  *     2. A . matching any character.
  832.  *     3. A ^ matching the begin of a line.
  833.  *     4. A $ (as last character of the pattern) mathing the end of a line.
  834.  *     5. A \<character> matching <character>.
  835.  *     6. A number of characters enclosed in [] pairs matching any of these
  836.  *        characters. A list of characters can be indicated by a '-'. So
  837.  *        [a-z] matches any letter of the alphabet. If the first character
  838.  *        after the '[' is a '^' then the set is negated (matching none of
  839.  *        the characters). 
  840.  *        A ']', '^' or '-' can be escaped by putting a '\' in front of it.
  841.  *     7. If one of the expressions as described in 1-6 is followed by a
  842.  *        '*' than that expressions matches a sequence of 0 or more of
  843.  *        that expression.
  844.  */
  845.  
  846.  
  847.  
  848. /* Expression definitions */
  849. #define NO_MATCH    0
  850. #define MATCH        1
  851. #define REG_ERROR    2
  852.  
  853. #define BEGIN_LINE    (2 * REG_ERROR)
  854. #define END_LINE    (2 * BEGIN_LINE)
  855.  
  856.  
  857. /*
  858.  * The regex structure. Status can be any of 0, BEGIN_LINE or REG_ERROR. In
  859.  * the last case, the result.err_mess field is assigned. Start_ptr and end_ptr
  860.  * point to the match found. For more details see the documentation file.
  861.  */
  862. struct regex {
  863.   union {
  864.       char *err_mess;
  865.       int *expression;
  866.   } result;
  867.   char status;
  868.   char *start_ptr;
  869.   char *end_ptr;
  870. };
  871.  
  872. typedef struct regex REGEX;
  873.  
  874. /* NULL definitions */
  875. #define NIL_PTR        ((char *) 0)
  876. #define NIL_REG        ((REGEX *) 0)
  877. #define NIL_INT        ((int *) 0)
  878.  
  879. /* Opcodes for characters */
  880. #define    NORMAL        0x0200
  881. #define DOT        0x0400
  882. #define EOLN        0x0800
  883. #define STAR        0x1000
  884. #define BRACKET        0x2000
  885. #define NEGATE        0x0100
  886. #define DONE        0x4000
  887.  
  888. /* Mask for opcodes and characters */
  889. #define LOW_BYTE    0x00FF
  890. #define HIGH_BYTE    0xFF00
  891.  
  892. /* Previous is the contents of the previous address (ptr) points to */
  893. #define previous(ptr)        (*((ptr) - 1))
  894.  
  895. /* Buffer to store outcome of compilation */
  896. int exp_buffer[BLOCK_SIZE];
  897.  
  898. /* Errors often used */
  899. char *too_long = "Regular expression too long";
  900.  
  901. /*
  902.  * Reg_error() is called by compile() is something went wrong. It set the
  903.  * status of the structure to error, and assigns the error field of the union.
  904.  */
  905. #define reg_error(str)    program->status = REG_ERROR, \
  906.                       program->result.err_mess = (str)
  907.  
  908. /*
  909.  * Compile compiles the pattern into a more comprehensible form and returns a 
  910.  * REGEX structure. If something went wrong, the status field of the structure
  911.  * is set to REG_ERROR and an error message is set into the err_mess field of
  912.  * the union. If all went well the expression is saved and the expression
  913.  * pointer is set to the saved (and compiled) expression.
  914.  */
  915. compile(pattern, program)
  916. register char *pattern;            /* Pointer to pattern */
  917. REGEX *program;
  918. {
  919.   register int *expression = exp_buffer;
  920.   int *prev_char;            /* Pointer to previous compiled atom */
  921.   int *acct_field;            /* Pointer to last BRACKET start */
  922.   int  negate;                /* Negate flag for BRACKET */
  923.   char low_char;            /* Index for chars in BRACKET */
  924.   char c;
  925.  
  926.   program->result.expression = expression;
  927.  
  928. /* Check for begin of line */
  929.   if (*pattern == '^') {
  930.       program->status = BEGIN_LINE;
  931.       pattern++;
  932.   }
  933.   else {
  934.       program->status = 0;
  935. /* If the first character is a '*' we have to assign it here. */
  936.       if (*pattern == '*') {
  937.           *expression++ = '*' + NORMAL;
  938.           pattern++;
  939.       }
  940.   }
  941.  
  942.   for (; ;) {
  943.       switch (c = *pattern++) {
  944.       case '.' :
  945.           *expression++ = DOT;
  946.           break;
  947.       case '$' :
  948.           /*
  949.            * Only means EOLN if it is the last char of the pattern
  950.            */
  951.           if (*pattern == '\0') {
  952.               *expression++ = EOLN | DONE;
  953.               program->status |= END_LINE;
  954.                 return;
  955.           }
  956.           else
  957.               *expression++ = NORMAL + '$';
  958.           break;
  959.       case '\0' :
  960.           *expression++ = DONE;
  961.             return;
  962.       case '\\' :
  963.           /* If last char, it must! mean a normal '\' */
  964.           if (*pattern == '\0')
  965.               *expression++ = NORMAL + '\\';
  966.           else
  967.               *expression++ = NORMAL + *pattern++;
  968.           break;
  969.       case '*' :
  970.           /*
  971.            * If the previous expression was a [] find out the
  972.            * begin of the list, and adjust the opcode.
  973.            */
  974.           prev_char = expression - 1;
  975.           if (*prev_char & BRACKET)
  976.               *(expression - (*acct_field & LOW_BYTE))|= STAR;
  977.           else
  978.               *prev_char |= STAR;
  979.           break;
  980.       case '[' :
  981.           /*
  982.            * First field in expression gives information about
  983.            * the list.
  984.            * The opcode consists of BRACKET and if necessary
  985.            * NEGATE to indicate that the list should be negated
  986.            * and/or STAR to indicate a number of sequence of this 
  987.            * list.
  988.            * The lower byte contains the length of the list.
  989.            */
  990.           acct_field = expression++;
  991.           if (*pattern == '^') {    /* List must be negated */
  992.               pattern++;
  993.               negate = TRUE;
  994.           }
  995.           else
  996.               negate = FALSE;
  997.           while (*pattern != ']') {
  998.               if (*pattern == '\0') {
  999.                   reg_error("Missing ]");
  1000.                   return;
  1001.               }
  1002.               if (*pattern == '\\')
  1003.                   pattern++;
  1004.               *expression++ = *pattern++;
  1005.               if (*pattern == '-') {
  1006.                           /* Make list of chars */
  1007.                   low_char = previous(pattern);
  1008.                   pattern++;    /* Skip '-' */
  1009.                   if (low_char++ > *pattern) {
  1010.                       reg_error("Bad range in [a-z]");
  1011.                       return;
  1012.                   }
  1013.                   /* Build list */
  1014.                   while (low_char <= *pattern)
  1015.                       *expression++ = low_char++;
  1016.                   pattern++;
  1017.               }
  1018.               if (expression >= &exp_buffer[BLOCK_SIZE]) {
  1019.                   reg_error(too_long);
  1020.                   return;
  1021.               }
  1022.           }
  1023.           pattern++;            /* Skip ']' */
  1024.           /* Assign length of list in acct field */
  1025.           if ((*acct_field = (expression - acct_field)) == 1) {
  1026.               reg_error("Empty []");
  1027.               return;
  1028.           }
  1029.           /* Assign negate and bracket field */
  1030.           *acct_field |= BRACKET;
  1031.           if (negate == TRUE)
  1032.               *acct_field |= NEGATE;
  1033.           /*
  1034.            * Add BRACKET to opcode of last char in field because
  1035.            * a '*' may be following the list.
  1036.            */
  1037.           previous(expression) |= BRACKET;
  1038.           break;
  1039.       default :
  1040.           *expression++ = c + NORMAL;
  1041.       }
  1042.       if (expression == &exp_buffer[BLOCK_SIZE]) {
  1043.           reg_error(too_long);
  1044.           return;
  1045.       }
  1046.   }
  1047.   /* NOTREACHED */
  1048. }
  1049.  
  1050. /* search the file for a regular expression. It may be :
  1051.  *
  1052.  *    a command line argument }   (string = regular expression)
  1053.  *    a user-entered string   }
  1054.  *    a repeat of the last string (string = NULL)
  1055.  */
  1056.  
  1057. search(string)
  1058. char *string;
  1059. {
  1060.   char *text,*match();
  1061.   static REGEX program;
  1062.   int loops,i,ret;
  1063.  
  1064.   if (string != NULL) {
  1065.     compile( string, &program);
  1066.     if (program.status == REG_ERROR) { 
  1067.         printf("%s%s %s%s",
  1068.             pclear,invon,program.result.err_mess,invoff);
  1069.         program.result.expression = NIL_INT;
  1070.         return 0;
  1071.     }
  1072.   }
  1073.  
  1074.   if (program.result.expression == NIL_INT) {
  1075.     printf("%s%s No previous search pattern%s",pclear,invon,invoff);
  1076.     return 0;
  1077.   }
  1078.  
  1079.   if (string == spattern)
  1080.     spattern = NULL;    /* global pattern now compiled */
  1081.  
  1082.   /* save current position */
  1083.   searchfrom = ftell(filep);
  1084.   searchline = lineno;
  1085.  
  1086.   loops = repeat;
  1087.   while ( (text = getline()) != NULL && quit == FALSE) {
  1088.  
  1089.       if (line_check(&program, text) == MATCH) {
  1090.  
  1091.         if (--loops > 0)    /* stop only when repeat exceeded */
  1092.             continue;
  1093.  
  1094.         /* line found - update screen */
  1095.           printf("\nskipping ... \n\n");
  1096.         for (i = 2; i >= 0; i--)
  1097.             if (i < linesused)
  1098.                 printf("%s\n",sline[i]);
  1099.         ret = lines(window-6);
  1100.  
  1101.         /* only slip off the bottom of the file if it's stdin. */
  1102.         if (filep == stdin)
  1103.             return ret;
  1104.         else
  1105.             return 0;
  1106.     }
  1107.   }
  1108.  
  1109.  
  1110.   /* string not found : print error and return to old position */
  1111.  
  1112.   if (quit)
  1113.     howmuch();
  1114.   else
  1115.     printf("%s%s string not found %s",pclear,invon,invoff);
  1116.  
  1117.   if (filep != stdin) {
  1118.     fseek(filep,searchfrom,0);
  1119.     lineno = searchline;
  1120.   }
  1121.   return 0;
  1122. }
  1123.  
  1124. /*
  1125.  * Line_check() checks the line (or rather string) for a match. 
  1126.  * It scans through the whole string
  1127.  * until a match is found, or the end of the string is reached.
  1128.  */
  1129. line_check(program, string)
  1130. register REGEX *program;
  1131. char *string;
  1132. {
  1133.   register char *textp = string;
  1134.  
  1135. /* Assign start_ptr field. We might find a match right away! */
  1136.   program->start_ptr = textp;
  1137.  
  1138. /* If the match must be anchored, just check the string. */
  1139.   if (program->status & BEGIN_LINE)
  1140.       return check_string(program, string, NIL_INT);
  1141.   
  1142.   /* Move through the string until the end of is found */
  1143.   while (quit == FALSE && *textp != '\0') {
  1144.       program->start_ptr = textp;
  1145.       if (check_string(program, textp, NIL_INT))
  1146.           return MATCH;
  1147.     if (*textp == '\n')
  1148.         break;
  1149.     textp++;
  1150.   }
  1151.  
  1152.   return NO_MATCH;
  1153. }
  1154.  
  1155. /*
  1156.  * Check() checks of a match can be found in the given string. Whenever a STAR
  1157.  * is found during matching, then the begin position of the string is marked
  1158.  * and the maximum number of matches is performed. Then the function star()
  1159.  * is called which starts to finish the match from this position of the string
  1160.  * (and expression). Check() return MATCH for a match, NO_MATCH is the string 
  1161.  * couldn't be matched or REG_ERROR for an illegal opcode in expression.
  1162.  */
  1163. check_string(program, string, expression)
  1164. REGEX *program;
  1165. register char *string;
  1166. int *expression;
  1167. {
  1168.   register int opcode;        /* Holds opcode of next expr. atom */
  1169.   char c;                /* Char that must be matched */
  1170.   char *mark;            /* For marking position */
  1171.   int star_fl;            /* A star has been born */
  1172.  
  1173.   if (expression == NIL_INT)
  1174.       expression = program->result.expression;
  1175.  
  1176. /* Loop until end of string or end of expression */
  1177.   while (quit == FALSE && !(*expression & DONE) &&
  1178.                        *string != '\0' && *string != '\n') {
  1179.       c = *expression & LOW_BYTE;      /* Extract match char */
  1180.       opcode = *expression & HIGH_BYTE; /* Extract opcode */
  1181.       if (star_fl = (opcode & STAR)) {  /* Check star occurrence */
  1182.           opcode &= ~STAR;      /* Strip opcode */
  1183.           mark = string;          /* Mark current position */
  1184.       }
  1185.       expression++;        /* Increment expr. */
  1186.       switch (opcode) {
  1187.       case NORMAL :
  1188.           if (star_fl)
  1189.               while (*string++ == c)    /* Skip all matches */
  1190.                   ;
  1191.           else if (*string++ != c)
  1192.               return NO_MATCH;
  1193.           break;
  1194.       case DOT :
  1195.           string++;
  1196.           if (star_fl)            /* Skip to eoln */
  1197.               while (*string != '\0' && *string++ != '\n')
  1198.                   ;
  1199.           break;
  1200.       case NEGATE | BRACKET:
  1201.       case BRACKET :
  1202.           if (star_fl)
  1203.               while (in_list(expression, *string++, c, opcode)
  1204.                                        == MATCH)
  1205.                   ;
  1206.           else if (in_list(expression, *string++, c, opcode) == NO_MATCH)
  1207.               return NO_MATCH;
  1208.           expression += c - 1;    /* Add length of list */
  1209.           break;
  1210.       default :
  1211.           panic("Corrupted program in check_string()","");
  1212.       }
  1213.       if (star_fl) 
  1214.           return star(program, mark, string, expression);
  1215.   }
  1216.   if (*expression & DONE) {
  1217.       program->end_ptr = string;    /* Match ends here */
  1218.       /*
  1219.        * We might have found a match. The last thing to do is check
  1220.        * whether a '$' was given at the end of the expression, or
  1221.        * the match was found on a null string. (E.g. [a-z]* always
  1222.        * matches) unless a ^ or $ was included in the pattern.
  1223.        */
  1224.       if ((*expression & EOLN) && *string != '\n' && *string != '\0')
  1225.           return NO_MATCH;
  1226.     if (string == program->start_ptr && !(program->status & BEGIN_LINE)
  1227.                      && !(*expression & EOLN))
  1228.           return NO_MATCH;
  1229.       return MATCH;
  1230.   }
  1231.   return NO_MATCH;
  1232. }
  1233.  
  1234. /*
  1235.  * Star() calls check_string() to find out the longest match possible.
  1236.  * It searches backwards until the (in check_string()) marked position
  1237.  * is reached, or a match is found.
  1238.  */
  1239. star(program, end_position, string, expression)
  1240. REGEX *program;
  1241. register char *end_position;
  1242. register char *string;
  1243. int *expression;
  1244. {
  1245.   do {
  1246.       string--;
  1247.       if (check_string(program, string, expression))
  1248.           return MATCH;
  1249.   } while (string != end_position);
  1250.  
  1251.   return NO_MATCH;
  1252. }
  1253.  
  1254. /*
  1255.  * In_list() checks if the given character is in the list of []. If it is
  1256.  * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
  1257.  * are reversed when the NEGATE field in the opcode is present.
  1258.  */
  1259. in_list(list, c, list_length, opcode)
  1260. register int *list;
  1261. char c;
  1262. register int list_length;
  1263. int opcode;
  1264. {
  1265.   if (c == '\0' || c == '\n')    /* End of string, never matches */
  1266.       return NO_MATCH;
  1267.   while (list_length-- > 1) {    /* > 1, don't check acct_field */
  1268.       if ((*list & LOW_BYTE) == c)
  1269.           return (opcode & NEGATE) ? NO_MATCH : MATCH;
  1270.       list++;
  1271.   }
  1272.   return (opcode & NEGATE) ? MATCH : NO_MATCH;
  1273. }
  1274.  
  1275.  
  1276.